﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Controls;
using System.Windows.Media;
using SDF.Pagination;

namespace SDF
{
    [ContentProperty("Blocks")]
    public class ListItem : SmartBlock, IBlockContainer
    {
        #region Constructor

        public ListItem()
        {
            Grid = new Grid();
            Grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(DEFAULT_ITEM_OFFSET) }); 
            Grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star)});

            Marker = new TextBlock();
            Marker.HorizontalAlignment = HorizontalAlignment.Right;
            Grid.Children.Add(Marker);

            Fragment = new Fragment() { Margin = new Thickness(0) };
            Grid.SetColumn(Fragment, 1);
            Grid.Children.Add(Fragment);

            SmartLogicalTreeHelper.AddChild(this, Fragment);
        }

        #endregion

        #region Properties

        protected Grid Grid { get; private set; }

        protected TextBlock Marker { get; private set; }

        protected Fragment Fragment { get; private set; }

        public SmartBlockCollection Blocks
        {
            get { return Fragment.Blocks; }
        }

        public List List
        {
            get { return SmartLogicalTreeHelper.GetParent(this) as List; }
        }

        #endregion

        #region Visual Children Implementation

        protected override int VisualChildrenCount
        {
            get { return 1; }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= 1)
                throw new ArgumentOutOfRangeException("index");

            return Grid;
        }

        #endregion

        #region Layout Methods

        protected override Size MeasureOverride(Size availableSize)
        {
            SetupMarker();

            Grid.Measure(availableSize);
            return Grid.DesiredSize;
        }

        protected override Size ArrangeOverride(Size availableSize)
        {
            Grid.Arrange(new Rect(availableSize));
            return availableSize;
        }

        #endregion

        #region SetActualPadding Method

        protected override Thickness CalculateActualPadding()
        {
            double left = Padding.Left;
            double top = Padding.Top;
            double right = Padding.Right;
            double bottom = Padding.Bottom;

            if (double.IsNaN(left))
                left = LineHeight;
            if (double.IsNaN(top))
                top = LineHeight;
            if (double.IsNaN(right))
                right = LineHeight;
            if (double.IsNaN(bottom))
                bottom = LineHeight;

            return new Thickness(left, top, right, bottom);
        }

        #endregion

        #region IsValidParent

        public override bool IsValidParent(DependencyObject parent)
        {
            return parent is List;
        }

        #endregion

        #region Marker Formatting

        private void SetupMarker()
        {
            var style = TextMarkerStyle.Disc;
            var font = this.FontFamily;
            var size = this.FontSize;
            var itemOffset = GetItemOffset(font, size);
            var index = 1;

            var list = this.List;
            if (list != null)
            {
                style = list.MarkerStyle;
                font = list.FontFamily;
                size = list.FontSize;
                itemOffset = GetItemOffset(font, size);
                index = list.Items.IndexOf(this) + 1;
            }

            var markerOffset = font.LineSpacing * size / 2;
            if ((list != null) && (!double.IsNaN(list.MarkerOffset)))
                markerOffset = list.MarkerOffset;

            Grid.ColumnDefinitions[0].Width = new GridLength(itemOffset);

            if (style != TextMarkerStyle.None)
            {
                Marker.Visibility = Visibility.Visible;
                Marker.Margin = new Thickness(0, 0, markerOffset, 0);
                Marker.FontSize = size;
                Marker.FontFamily = font;

                switch (style)
                {
                    case TextMarkerStyle.Circle:
                        Marker.Text = "\x00a1";
                        Marker.FontFamily = WINGDINGS;
                        break;

                    case TextMarkerStyle.Square:
                        Marker.Text = "q";
                        Marker.FontFamily = WINGDINGS;
                        break;

                    case TextMarkerStyle.Box:
                        Marker.Text = "\x00a7";
                        Marker.FontFamily = WINGDINGS;
                        break;

                    case TextMarkerStyle.Decimal:
                        Marker.Text = String.Format("{0}.", index);
                        break;

                    case TextMarkerStyle.LowerLatin:
                        Marker.Text = GetLatinText(index);
                        break;

                    case TextMarkerStyle.UpperLatin:
                        Marker.Text = GetLatinText(index).ToUpper();
                        break;

                    case TextMarkerStyle.LowerRoman:
                        Marker.Text = GetRomanText(index);
                        break;

                    case TextMarkerStyle.UpperRoman:
                        Marker.Text = GetRomanText(index).ToUpper();
                        break;

                    default:
                        Marker.Text = "\x009f";
                        Marker.FontFamily = WINGDINGS;
                        break;
                }
            }
            else
            {
                Marker.Visibility = Visibility.Collapsed;
            }
        }


        private static string GetLatinText(int index)
        {
            return GetMarkerText(index, LATIN_SYMBOLS);
        }

        private static string GetMarkerText(int index, string symbols)
        {
            // We base our calculations on a zero-based array, but the user passes a 1 based count
            index--;

            var result = new StringBuilder();
            int length = symbols.Length;

            int remaining = index;
            if (index > length)
            {
                remaining = index % length;
                result.Append(GetMarkerText(index / length, symbols));
            }

            result.Append(symbols[remaining]);
            result.Append(".");

            return result.ToString();
        }

        // Code copied from http://www.blackwasp.co.uk/NumberToRoman.aspx
        private static string GetRomanText(int index)
        {
            // Validate
            if (index < 0 || index > 3999)
                throw new ArgumentException("Value must be in the range 0 - 3,999.");

            if (index == 0) return "N";

            //Setup key numerals and numeral pairs
            int[] values = new int[] { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 };
            string[] numerals = new string[] { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" };

            // Initialise the string builder
            var result = new StringBuilder();

            // Loop through each of the values to diminish the number
            for (int i = 0; i < 13; i++)
            {
                // If the number being converted is less than the test value, append
                // the corresponding numeral or numeral pair to the resultant string
                while (index >= values[i])
                {
                    index -= values[i];
                    result.Append(numerals[i]);
                }
            }
            result.Append(".");

            return result.ToString();
        }

        protected virtual double GetItemOffset(FontFamily font, double size)
        {
            double result = font.LineSpacing * size * 3;
            return result;
        }

        #endregion

        #region Constants

        private const int DEFAULT_ITEM_OFFSET = 77; // 77 = 0.75 inch

        private const string LATIN_SYMBOLS = "abcdefghijklmnopqrstuvwxyz";

        protected static readonly FontFamily WINGDINGS = new FontFamily("Wingdings");

        #endregion

        #region Harvesting

        public override HarvestResult Harvest(HarvestingContext context, double remainingHeight, SmartBlock previousBlock)
        {
            bool isPartiallyHarvested = IsPartiallyHarvested(context);

            // If this is the first part, count the margin
            var harvestedMargin = this.ActualMargin;
            if (isPartiallyHarvested)
                harvestedMargin.Top = 0;
            else if ((previousBlock == null) && double.IsNaN(this.Margin.Top))
                harvestedMargin.Top = 0;
            remainingHeight -= harvestedMargin.Top;


            // If we can't fit the content, then break the column
            var childResult = Fragment.Harvest(context, remainingHeight, null);
            if (childResult.HarvestedBlock == null)
            {
                return HarvestResult.BreakColumn;
            }
            remainingHeight = childResult.RemainingHeight;

            var harvestedElement = GetEmptyCopy() as ListItem;

            // Replace the default Fragment with the harvested one
            var harvestedFragment = childResult.HarvestedBlock as Fragment;
            while (harvestedFragment.Blocks.Count > 0)
            {
                var block = harvestedFragment.Blocks[0];
                harvestedFragment.Blocks.RemoveAt(0);
                harvestedElement.Blocks.Add(block);
            }

            if (context.IsFullyHarvested(Fragment))
                context.MarkAsFullyHarvested(this);
            else
                harvestedMargin.Bottom = 0; // We are going to split, so remove the bottom margin

            if (isPartiallyHarvested)
                harvestedElement.Marker.Visibility = Visibility.Collapsed;


            harvestedElement.Margin = harvestedMargin;
            remainingHeight -= harvestedMargin.Bottom;
            return new HarvestResult(harvestedElement, remainingHeight, false, false);
        }

        public override SmartBlock GetEmptyCopy()
        {
            var result = new ListItem();
            CopyProperties(result);
            return result;
        }

        #endregion

        #region CalculateMinHeight

        public override double CalculateMinHeight()
        {
            if (Blocks.Count > 0)
            {
                var firstBlock = Blocks[0];
                if (firstBlock != null)
                    return firstBlock.CalculateMinHeight();
            }

            return this.LineHeight;
        }

        #endregion

    }
}
